﻿namespace WaferService
{
    public interface IEventStream
    {
        IEventStreamScope NewScope(Substrate substrate);

        Task StartTransfer();

        Task EndTransfer();
    }

    public interface IEventStreamScope : IAsyncDisposable
    {
        public Substrate Substrate { get; }

        Task SetState(WaferState nextState);

        Task MoveTo(WaferLocation to);

        Task MoveTo(WaferLocation loc, WaferState nextState);

        Task MoveTo(int foup, int slot, WaferLocation from, WaferLocation to, WaferState state);

        Task Swap(WaferLocation locIn, int foupOut, int slotOut, WaferLocation locOut);

        void Complete();
    }

    public class EventStream(Guid guid, SubstrateTrackingService service) : IEventStream
    {
        int hasError;

        public async Task StartTransfer()
        {
            await Send(new WaferTransferStartEvent
            {
                TrackingId = guid,
            });
        }

        public async Task EndTransfer()
        {
            await Send(new WaferTransferEndEvent
            {
                TrackingId = guid,
            });
        }

        public IEventStreamScope NewScope(Substrate substrate)
        {
            return new EventStreamScope(guid, substrate, this);
        }

        public async Task Send(SubstrateTrackingEvent evt)
        {
            await service.SendEvent(evt);
        }

        public async Task SendError(WaferTransferErrorEvent evt)
        {
            if (Interlocked.CompareExchange(ref hasError, 1, 0) == 0)
            {
                await service.SendEvent(evt);
            }
        }
    }

    public sealed class EventStreamScope(Guid guid,
        Substrate substrate,
        EventStream eventStream) : IEventStreamScope
    {
        bool completed;

        readonly int foup = substrate.Foup;
        readonly int slot = substrate.Slot;
        WaferLocation from = substrate.Location;
        WaferState state = substrate.State;

        public Substrate Substrate => new()
        {
            Slot = slot,
            Foup = foup,
            Location = from,
            State = state
        };

        public async Task SetState(WaferState nextState)
        {
            await eventStream.Send(new WaferStateChangedEvent
            {
                TrackingId = guid,
                Foup = foup,
                Slot = slot,
                State = nextState
            });

            state = nextState;
        }

        public async Task MoveTo(WaferLocation to)
        {
            await eventStream.Send(new WaferTransferEvent
            {
                TrackingId = guid,
                Foup = foup,
                Slot = slot,
                From = from,
                To = to,
            });

            from = to;
        }

        public async Task MoveTo(WaferLocation to, WaferState nextState)
        {
            await MoveTo(foup, slot, from, to, nextState);
            from = to;
            state = nextState;
        }

        public async Task MoveTo(int foup, int slot, WaferLocation from, WaferLocation to, WaferState state)
        {
            await eventStream.Send(new WaferTransferEvent
            {
                TrackingId = guid,
                Foup = foup,
                Slot = slot,
                From = from,
                To = to,
            });

            await eventStream.Send(new WaferStateChangedEvent
            {
                TrackingId = guid,
                Foup = foup,
                Slot = slot,
                State = state
            });
        }

        public async Task Swap(WaferLocation locIn, int foupOut, int slotOut, WaferLocation locOut)
        {
            await eventStream.Send(new WaferSwapEvent
            {
                TrackingId = guid,
                FoupIn = foup,
                SlotIn = slot,
                FoupOut = foupOut,
                SlotOut = slotOut,
                LocationIn = locIn,
                LocationOut = locOut
            });

            from = locIn;
        }

        public void Complete()
        {
            completed = true;
        }

        public async ValueTask DisposeAsync()
        {
            if (completed)
            {
                return;
            }

            await eventStream.Send(new WaferStateChangedEvent
            {
                TrackingId = guid,
                Foup = foup,
                Slot = slot,
                State = WaferState.Error
            });

            await eventStream.SendError(new WaferTransferErrorEvent
            {
                TrackingId = guid,
                Message = "Transfer error"
            });
        }
    }
}
